package jlib.component.tool.handler
{
	import flash.display.BitmapData;
	import flash.display.DisplayObject;
	import flash.display.DisplayObjectContainer;
	import flash.display.Graphics;
	import flash.display.Shape;
	import flash.display.Sprite;
	import flash.display.Stage;
	import flash.events.Event;
	import flash.events.MouseEvent;
	import flash.geom.Matrix;
	import flash.geom.Point;
	import flash.geom.Rectangle;
	import flash.ui.Mouse;
	import flash.ui.MouseCursor;
	
	import jlib.component.tool.TransformMode;
	import jlib.component.tool.event.TransformHandlerEvent;
	import jlib.component.tool.skin.points.AbstractControlPoint;
	import jlib.component.tool.skin.points.ControlPoint;
	import jlib.component.tool.skin.points.decorator.DecoratorFactory;
	import jlib.component.tool.skin.points.decorator.MoveDecorator;
	import jlib.component.tool.skin.points.decorator.RotateDecorator;
	import jlib.component.tool.skin.points.decorator.ScaleBothDecorator;
	import jlib.component.tool.skin.points.decorator.ScaleXDecorator;
	import jlib.component.tool.skin.points.decorator.ScaleYDecorator;
	import jlib.utils.MathUtil;
	import jlib.utils.MatrixUtil;
	import jlib.utils.RectangleUtil;
	import jlib.utils.MatrixUtil;
	
	public class MatrixTransformHandler extends AbstractTransformHandler {
		
		static private const TOP_LEFT:int = 0;
		static private const TOP_CENTER:int = 1;
		static private const TOP_RIGHT:int = 2;
		static private const MIDDLE_LEFT:int = 3;
		static private const MIDDLE_CENTER:int = 4;
		static private const MIDDLE_RIGHT:int = 5;
		static private const BOTTOM_LEFT:int = 6;
		static private const BOTTOM_CENTER:int = 7;
		static private const BOTTOM_RIGHT:int = 8;
		
		private var m_points:Vector.<DisplayObject>;
		
		private var m_bound:Rectangle;
		private var m_matrix:Matrix;
		private var m_startMatrix:Matrix;
		private var m_invMatrix:Matrix;
		private var m_invStartMatrix:Matrix;
		
		private var m_pressPoint:Point;
		private var m_movePoint:Point;
		private var m_innerPressPoint:Point;
		private var m_innerMovePoint:Point;
		
		private var m_pressRadian:Number;
		private var m_transformMode:String;
		
		private var m_canvas:DisplayObjectContainer;
		private var m_outline:Sprite;
		
		private var m_innerRegistration:Point;
		private var m_registration:Point;
		private var m_visible:Boolean;
		
		private var m_list:Array;
		private var m_matrixList:Vector.<Matrix>;
		
		public function MatrixTransformHandler()
		{
			m_matrixList = new Vector.<Matrix>();
			m_list = new Array();
			m_matrix = new Matrix();
			m_registration = new Point();
			m_innerRegistration = new Point();
			m_pressPoint = new Point();
			m_movePoint = new Point();
			m_bound = new Rectangle();
			m_points = new Vector.<DisplayObject>();
			
			for(var i:int=0;i<9;i++){
				m_points.push(createDefaultPointSkin(i));
			}
		}
		
		static private function createDefaultPointSkin($index:int): DisplayObject {
			var result:AbstractControlPoint = new ControlPoint();
			var decoFactory:DecoratorFactory = new DecoratorFactory();
			
			result.buttonMode = true;
			
			if($index == TOP_LEFT || $index == TOP_RIGHT || $index == BOTTOM_LEFT || $index == BOTTOM_RIGHT) {
				result.addDecorator(new RotateDecorator(decoFactory.getSkin(TransformMode.ROTATE), decoFactory.getCursor(TransformMode.ROTATE)));
				result.addDecorator(new ScaleBothDecorator(decoFactory.getSkin(TransformMode.SCALE_BOTH), decoFactory.getCursor(TransformMode.SCALE_BOTH)));
			} else if($index == MIDDLE_CENTER){
				result.addDecorator(new MoveDecorator(decoFactory.getSkin(TransformMode.MOVE), decoFactory.getCursor(TransformMode.MOVE)));
			} else if($index == MIDDLE_LEFT || $index == MIDDLE_RIGHT){
				result.addDecorator(new ScaleXDecorator(decoFactory.getSkin(TransformMode.SCALE_X), decoFactory.getCursor(TransformMode.SCALE_X)));
			} else {
				result.addDecorator(new ScaleYDecorator(decoFactory.getSkin(TransformMode.SCALE_Y), decoFactory.getCursor(TransformMode.SCALE_Y)));
			}
			return result;
		}
		
		override public function press( e:MouseEvent ): void {
			
			m_pressPoint.x = m_canvas.mouseX;
			m_pressPoint.y = m_canvas.mouseY;
			
			m_startMatrix = m_matrix.clone();
			m_invStartMatrix = m_startMatrix.clone();
			m_invStartMatrix.invert();
			
			m_pressRadian = MatrixUtil.getRotationRadians(m_matrix);
			m_innerPressPoint = m_invStartMatrix.transformPoint(m_pressPoint);
			
			if(e.target is AbstractControlPoint) {
				var point:AbstractControlPoint = e.target as AbstractControlPoint;
				m_transformMode = point.getMode();
			} else if (e.target == m_canvas || e.target is Stage){
				m_transformMode = TransformMode.NONE;
			} else m_transformMode = TransformMode.MOVE;
			
			update();
		}
		
		override public function move(e:MouseEvent): void {
			m_movePoint.x =	m_canvas.mouseX;
			m_movePoint.y = m_canvas.mouseY;
			
			m_innerMovePoint = m_invStartMatrix.transformPoint(m_movePoint);
			
			if(m_transformMode == TransformMode.MOVE) {
				translate(e);
			}else if(m_transformMode == TransformMode.ROTATE) {
				rotate(e);
			}else if(m_transformMode == TransformMode.SCALE_X) {
				scaleXControl(e);
			}else if(m_transformMode == TransformMode.SCALE_Y) {
				scaleYControl(e);
			}else if(m_transformMode == TransformMode.SCALE_BOTH){
				scaleBothControl(e);
			}
			update();
			apply();
		}
		
		override public function release( e:MouseEvent ): void {
			update();
			m_transformMode = TransformMode.NONE;
			if(!MatrixUtil.equals(m_startMatrix,m_matrix)){
				this.dispatchEvent(new TransformHandlerEvent(TransformHandlerEvent.CHANGED_TRANSFORM));
			}
		}
		
		override public function setOutline($outline:Sprite): void {
			m_outline = $outline;
			for each( var point:DisplayObject in m_points ) {
				if(!m_outline.contains(point)) {
					m_outline.addChild(point);	
				}
			}
			m_outline.addChild(m_points[MIDDLE_CENTER]);
			update();
		}
		
		override public function setTargets($list:Array): void
		{
			m_list = $list;
			m_matrix = new Matrix();
			m_bound = RectangleUtil.totalBounds($list, m_canvas);
			if(!m_bound) m_bound = new Rectangle();
			m_matrixList.length = 0;
			for each(var graphic:DisplayObject in m_list){
				m_matrixList.push(graphic.transform.matrix);
			}
			update();
		}
		
		private function translate(e:Event): void 
		{	
			m_matrix.tx = m_startMatrix.tx + (m_movePoint.x - m_pressPoint.x);
			m_matrix.ty = m_startMatrix.ty + (m_movePoint.y - m_pressPoint.y);
		}
		
		private function rotate(e:MouseEvent): void
		{	
			var point:Point = m_matrix.transformPoint(m_innerRegistration);
			var innerStart:Point = point.subtract(m_pressPoint);
			var innerEnd:Point = point.subtract(m_movePoint);
			var radian:Number = Math.atan2(innerEnd.y,innerEnd.x)-Math.atan2(innerStart.y, innerStart.x);
			
			MatrixUtil.setRotationRadians(m_matrix, m_pressRadian+radian);
			updateTranslate();
		}
		
		private function scaleXControl(e:MouseEvent): void 
		{
			var innerDistanceX:Number = m_innerPressPoint.x-m_innerRegistration.x;
			var ratioH:Number = (m_innerMovePoint.x-m_innerPressPoint.x)/innerDistanceX;
			
			if(isNaN(ratioH) || Math.abs(ratioH) == 1) return;
			
			m_matrix.a = m_startMatrix.a+(m_startMatrix.a*ratioH);
			m_matrix.b = m_startMatrix.b+(m_startMatrix.b*ratioH);
			updateTranslate();
		}
		
		private function scaleYControl(e:MouseEvent): void 
		{
			var innerDistanceY:Number = m_innerPressPoint.y-m_innerRegistration.y;
			var ratioV:Number = (m_innerMovePoint.y-m_innerPressPoint.y)/innerDistanceY;
			
			if(isNaN(ratioV) || Math.abs(ratioV) == 1) return;
			
			m_matrix.c = m_startMatrix.c+(m_startMatrix.c*ratioV);
			m_matrix.d = m_startMatrix.d+(m_startMatrix.d*ratioV);
			updateTranslate();
		}
		
		private function scaleBothControl(e:MouseEvent): void 
		{
			var innerDistanceX:Number = m_innerPressPoint.x-m_innerRegistration.x;
			var innerDistanceY:Number = m_innerPressPoint.y-m_innerRegistration.y;
			
			var ratioH:Number = (m_innerMovePoint.x-m_innerPressPoint.x)/innerDistanceX;
			var ratioV:Number = (m_innerMovePoint.y-m_innerPressPoint.y)/innerDistanceY;
			
			if(isNaN(ratioH) || Math.abs(ratioH) == 1 || isNaN(ratioV) || Math.abs(ratioV) == 1) {
				return;
			}
			
			m_matrix.a = m_startMatrix.a+(m_startMatrix.a*ratioH);
			m_matrix.b = m_startMatrix.b+(m_startMatrix.b*ratioH);
			m_matrix.c = m_startMatrix.c+(m_startMatrix.c*ratioV);
			m_matrix.d = m_startMatrix.d+(m_startMatrix.d*ratioV);
			
			var a:Number = Math.abs(m_matrix.a);
			var b:Number = Math.abs(m_matrix.b);
			var c:Number = Math.abs(m_matrix.c);
			var d:Number = Math.abs(m_matrix.d);
			
			if(0 < a && a < 1e-2) m_matrix.a = revisionMatrixValue(m_matrix.a);
			if(0 < b && b < 1e-2) m_matrix.b = revisionMatrixValue(m_matrix.b);
			if(0 < c && c < 1e-2) m_matrix.c = revisionMatrixValue(m_matrix.c);
			if(0 < d && d < 1e-2) m_matrix.d = revisionMatrixValue(m_matrix.d);
			
			//trace("r:", m_matrix, m_innerMovePoint.x-m_innerPressPoint.x, m_innerMovePoint.y-m_innerPressPoint.y);
			updateTranslate();
		}
		
		private function revisionMatrixValue($number:Number): Number
		{
			var result:Number;
			if($number > 0){
				result = 0.01;
			}else if($number < 0) {
				result = -0.01;
			}
			return result; 
		}
		
		override public function apply():void
		{
			for(var i:int=0;i<m_list.length;i++){
				var gmat:Matrix = m_matrix.clone();
				if(!gmat) break;
				var lmat:Matrix = m_matrixList[i].clone();
				lmat.concat(gmat);
				//m_list[i].getTransform().setMatrix(lmat);
				m_list[i].transform.matrix = lmat;
			}
		}
		
		override public function update(event:Event=null): void
		{
			m_invMatrix = m_matrix.clone();
			m_invMatrix.invert();
			
			var boundMatrix:Matrix = new Matrix();
			boundMatrix.concat(m_matrix);
			boundMatrix.concat(m_canvas.transform.matrix);
			
			update9PointsPosition(m_points, transformRect(m_bound, boundMatrix));
			updateRegistration();
			updateCursor();
			drawMatrixBound(m_outline.graphics, m_bound, boundMatrix);
		}
		
		private function updateTranslate(): void 
		{
			var point:Point = m_matrix.transformPoint(m_innerRegistration);
			var offset:Point = m_registration.subtract(point);
			
			m_matrix.tx += offset.x;
			m_matrix.ty += offset.y;
		}
		
		private function updateRegistration(): void 
		{
			var invCanvas:Matrix = m_canvas.transform.matrix;
			invCanvas.invert();
			m_registration = invCanvas.transformPoint(new Point(m_points[MIDDLE_CENTER].x, m_points[MIDDLE_CENTER].y))
			m_innerRegistration = m_invMatrix.transformPoint(m_registration);
		}
		
		private function update9PointsPosition($points:Vector.<DisplayObject>, $boundPoints:Array): void
		{
			var topCenter:Point = Point.interpolate($boundPoints[0], $boundPoints[1],.5);
			var midLeft:Point = Point.interpolate($boundPoints[0], $boundPoints[3],.5);
			var midCenter:Point = Point.interpolate($boundPoints[0], $boundPoints[2],.5);
			var midRight:Point = Point.interpolate($boundPoints[1], $boundPoints[2],.5);
			var bottomCenter:Point = Point.interpolate($boundPoints[2], $boundPoints[3],.5);
			
			$points[TOP_LEFT].x = $boundPoints[0].x , $points[TOP_LEFT].y = $boundPoints[0].y;
			$points[TOP_CENTER].x = topCenter.x, $points[TOP_CENTER].y = topCenter.y;
			$points[TOP_RIGHT].x = $boundPoints[1].x , $points[TOP_RIGHT].y = $boundPoints[1].y;
			$points[MIDDLE_LEFT].x = midLeft.x , $points[MIDDLE_LEFT].y = midLeft.y;
			$points[MIDDLE_CENTER].x = midCenter.x, $points[MIDDLE_CENTER].y = midCenter.y;
			$points[MIDDLE_RIGHT].x = midRight.x , $points[MIDDLE_RIGHT].y = midRight.y;
			$points[BOTTOM_LEFT].x = $boundPoints[2].x , $points[BOTTOM_LEFT].y = $boundPoints[2].y;
			$points[BOTTOM_CENTER].x = bottomCenter.x, $points[BOTTOM_CENTER].y = bottomCenter.y;
			$points[BOTTOM_RIGHT].x = $boundPoints[3].x , $points[BOTTOM_RIGHT].y = $boundPoints[3].y;
		}
		
		private function drawMatrixBound($g:Graphics, $bound:Rectangle, $matrix:Matrix):void
		{
			if($bound.isEmpty()) return ;
			$g.clear();
			
			var points:Array = transformRect($bound, $matrix);
			
			$g.lineStyle(1,0xcccccc);
			$g.moveTo(points[0].x, points[0].y);
			$g.lineTo(points[1].x, points[1].y);
			$g.lineTo(points[2].x, points[2].y);
			$g.lineTo(points[3].x, points[3].y);
			$g.lineTo(points[0].x, points[0].y);
		}
		
		private function updateCursor(): void
		{
			var distinctionAngle:Number = Math.atan2(m_points[TOP_LEFT].y-m_points[MIDDLE_CENTER].y, m_points[TOP_LEFT].x-m_points[MIDDLE_CENTER].x)*180/Math.PI-MatrixUtil.getRotation(m_matrix);
			if( int(distinctionAngle/360) != 0 ) distinctionAngle = distinctionAngle - 360*int(distinctionAngle/360);
			if(0 < distinctionAngle && distinctionAngle < 90){
				updateRotateCursor(true);
			}else if(90 < distinctionAngle && distinctionAngle < 180){
				updateRotateCursor(false);
			}else if(-180 < distinctionAngle && distinctionAngle < -90 ){
				updateRotateCursor(true);
			}else if(180 < distinctionAngle && distinctionAngle < 270){
				updateRotateCursor(true);
			}else if(-90 < distinctionAngle && distinctionAngle < 0 ){
				updateRotateCursor(false);
			}else if(270 < distinctionAngle && distinctionAngle < 360){
				updateRotateCursor(false);
			}
		}
		
		private function updateRotateCursor($sign:Boolean): void {
			var sign:int = 1;
			if($sign == false) sign = -1;
			m_points[TOP_LEFT].rotation = MatrixUtil.getRotation(m_matrix)+45*sign;
			m_points[TOP_CENTER].rotation = MatrixUtil.getRotation(m_matrix)+90*sign;
			m_points[TOP_RIGHT].rotation = MatrixUtil.getRotation(m_matrix)+135*sign;
			m_points[MIDDLE_LEFT].rotation = MatrixUtil.getRotation(m_matrix);
			m_points[MIDDLE_RIGHT].rotation = MatrixUtil.getRotation(m_matrix);
			m_points[BOTTOM_LEFT].rotation = MatrixUtil.getRotation(m_matrix)+(-135*sign);
			m_points[BOTTOM_CENTER].rotation = MatrixUtil.getRotation(m_matrix)+(-90*sign);
			m_points[BOTTOM_RIGHT].rotation = MatrixUtil.getRotation(m_matrix)+(-45*sign);
			
		}
		
		override public function getMatrix(): Matrix { return m_matrix; }
		
		override public function setMatrix($matrix:Matrix): void {
			m_matrix = $matrix;
		}
		
		override public function setCanvas($canvas:DisplayObjectContainer): void {
			m_canvas = $canvas;
		}
		
		override public function getVisible(): Boolean { return m_visible; }
		
		override public function setVisible($value:Boolean): void { 
			m_visible = $value;
			for each(var point:ControlPoint in m_points) {
				point.visible = m_visible;
			} 
		}
		
		override public function dispose(): void {
			for each(var point:ControlPoint in m_points) {
				point.dispose();	
			}
		}
		
		private function transformRect($bound:Rectangle, $matrix:Matrix):Array {
			var points:Array = [$bound.topLeft, new Point($bound.right, $bound.top), 
				$bound.bottomRight, new Point($bound.left, $bound.bottom)];
			for(var i:int=0;i<points.length;++i){
				points[i] = $matrix.transformPoint(points[i]);
			}
			return points;
		}
		
	}
}